﻿namespace GreenIsomer.Web.Controls.JQuery {
	using System;
	using System.Web.UI;
	using System.Web.UI.WebControls;
	using System.ComponentModel;
	using System.Globalization;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Server control used to generate a jQuery UI Tabs widget.
	/// </summary>
	[ParseChildren(false), PersistChildren(true)]
	public class Tabs : JQueryWebControl {


		#region Constants: Default properties

		private const PanelActivateEvent defaultEvent = PanelActivateEvent.Click;
		private const int defaultSelected = 0;
		private const bool defaultSortable = false;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the GreenIsomer.Web.Controls.JQuery.Tabs class.
		/// </summary>
		public Tabs() : base(HtmlTextWriterTag.Div) {
			SelectedField = null;
		}

		#endregion


		#region Properties: Widget initialization options
		
		/// <summary>
		/// The type of event to be used for selecting a tab.
		/// This property maps directly to the "event" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultEvent), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Event", Justification = "Exposed as the jQuery option name.")]
		public virtual PanelActivateEvent Event {
			get { return ViewState.GetValue("Event", defaultEvent); }
			set {
				if(!Enum.IsDefined(typeof(PanelActivateEvent), value)) throw new ArgumentException("Value not defined.", "value");
				ViewState["Event"] = value;
			}
		}

		//TODO: This is simply too fragile.  Lets call it ActivePanel and change it to use a ViewState control name reference, and set field with clientId, init with visible index and postback with clientId
		/// <summary>
		/// Zero-based index of the tab to be selected on initialization.
		/// This property maps directly to the "selected" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultSelected), Category("Appearance")]
		public virtual int? Selected {
			get {
				EnsureChildControls();
				string value = SelectedField.Value;
				return string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value, CultureInfo.InvariantCulture);
			}
			set {
				EnsureChildControls();
				SelectedField.Value = value.HasValue ? value.Value.ToString(CultureInfo.InvariantCulture) : string.Empty;
			}
		}

		/// <summary>
		/// Enables the ability for the user to rearrange the tab order.
		/// </summary>
		[DefaultValue(defaultSortable), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sortable", Justification = "Exposed as the jQuery option name.")]
		public virtual bool Sortable {
			get { return ViewState.GetValue("Sortable", defaultSortable); }
			set { ViewState["Sortable"] = value; }
		}

		#endregion


		#region Properties: Sub control

		/// <summary>
		/// Gets or sets the hidden field used to round-trip the Selected property.
		/// </summary>
		protected virtual PostBackField SelectedField { get; set; }

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the Selected property.
		/// </summary>
		public virtual string SelectedClientId {
			get { return SelectedField.ClientID; }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the Selected property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler SelectedChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the SelectedChanged event.
		/// </summary>
		/// <param name="e">The System.EventArgs object that contains the event data.</param>
		protected virtual void OnSelectedChanged(EventArgs e)
		{
			if (SelectedChanged != null) SelectedChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The GreenIsomer.Web.Controls.JQuery.Manager used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterScriptFile(Settings.Default.CoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiCoreScriptFile);
			manager.RegisterScriptFile(Settings.Default.UiTabsScriptFile);

			if(Sortable) manager.RegisterScriptFile(Settings.Default.UiSortableScriptFile);

			manager.RegisterStyleFile(Settings.Default.UiCoreStyleFile);
			manager.RegisterStyleFile(Settings.Default.UiTabsStyleFile);
			manager.RegisterThemeStyleFile();

			manager.RegisterControl(this);

			EnsureChildControls();
		}

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparatin for posting back or rendering.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void CreateChildControls() {
			SelectedField = new PostBackField();
			//TODO: This has to be resolved!
			SelectedField.ID = ID + ClientIDSeparator + "Selected";
			SelectedField.Value = defaultSelected.ToString(CultureInfo.InvariantCulture);
			SelectedField.ValueChanged += new EventHandler<PostBackFieldEventArgs>(SelectedField_ValueChanged);
			Controls.Add(SelectedField);

			base.CreateChildControls();
		}

		/// <summary>
		/// Sends server control content to a provided System.Web.UI.HtmlTextWriter object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A System.Web.UI.HtmlTextWriter that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling)
				writer.AddStyleAttribute("display", "none");

			base.Render(writer);
		}

		/// <summary>
		/// Outputs the content of a server control's children to a provided System.Web.UI.HtmlTextWriter object, which writes the content to be rendered on the client.
		/// This property is used primarily by control developers.
		/// </summary>
		/// <param name="writer">The System.Web.UI.HtmlTextWriter object that receives the rendered content.</param>
		protected override void RenderChildren(HtmlTextWriter writer) {

			writer.Indent++;

			bool first = true;
			if(Controls.Count != 0) {
				writer.RenderBeginTag(HtmlTextWriterTag.Ul);
				foreach(Control control in Controls) {
					if(control is LiteralControl || control is PostBackField) continue;

					TabPanel tabPanel = control as TabPanel;

					if(first) first = false;
					else writer.WriteLine();

					//<li>
					writer.RenderBeginTag(HtmlTextWriterTag.Li);

					//<a>
					string ajaxContentUrl = tabPanel == null ? null : tabPanel.AjaxContentUrl;
					writer.AddAttribute(HtmlTextWriterAttribute.Href, string.IsNullOrEmpty(ajaxContentUrl) ? "#" + control.ClientID : ResolveUrl(ajaxContentUrl));
					writer.RenderBeginTag(HtmlTextWriterTag.A);
					if(tabPanel != null) writer.WriteEncodedText(tabPanel.TabText);
					writer.RenderEndTag();
					//</a>

					writer.RenderEndTag();
					//</li>
				}
				writer.RenderEndTag();
			}

			base.RenderChildren(writer);
		}

		/// <summary>
		/// Renders the load script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderLoadScript(ScriptBuilder script) {
			string clientId = ClientID;

			script.RenderSelector(clientId);
			script.Append(".tabs(");
			script.RenderProperties(CreateProperties());
			script.Append(")");

			if(Sortable) RenderSortableScriptChain(script);

			script.Append(";");

			//TODO: Let's revisit the whole "degradability versus getting rid of flicker issue"  I don't think this falls under UseCssStyling, perhaps a DegradeForNonJs... Or is that a moot point here?
			if(!MasterUseCssStyling) {
				script.Append(" ");
				script.RenderSelector(clientId);
				script.Append(".css('display', '');");
			}
		}

		/// <summary>
		/// Renders the sortable suffix chained onto the initialization options for the widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sortable", Justification = "Sortable is JQuery's word.  Used here for consistency.")]
		protected virtual void RenderSortableScriptChain(ScriptBuilder script) {
			script.Append(".find(");
			script.AppendValue(".ui-tabs-nav");
			script.Append(").sortable()");
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <returns>A GreenIsomer.Web.Controls.JQuery.Properties object that will become the jQuery UI initialization options.</returns>
		protected override PropertyCollection CreateProperties() {
			PropertyCollection properties = base.CreateProperties();

			PanelActivateEvent activateEvent = Event;
			if(Event != defaultEvent) properties.AddEnumValue("event", activateEvent);

			int? selected = Selected;
			if(selected != defaultSelected) {
				if(selected.HasValue) properties.AddValue("selected", selected.Value);
				else properties.AddValue("selected", false);
			}

			ScriptBuilder selectScript = new ScriptBuilder();
			RenderSelectScript(selectScript);
			if(selectScript.Length != 0) properties.AddScript("select", selectScript);

			return properties;
		}

		/// <summary>
		/// Renders the client "change" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A GreenIsomer.Web.Controls.JQuery.ScriptBuilder that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderSelectScript(ScriptBuilder script) {

			script.Append("function(event,ui){ ");

			script.RenderSelector(SelectedClientId);
			script.Append(".val(ui.index); ");

			script.Append("}");

		}

		#endregion


		#region Event handlers

		private void SelectedField_ValueChanged(object sender, PostBackFieldEventArgs e) {
			OnSelectedChanged(EventArgs.Empty);
		}

		#endregion


	}

}